[Go] Add Go bindings for ONNX Runtime C API#29615
Open
dannyota wants to merge 4 commits into
Open
Conversation
Idiomatic Go wrapper for the ORT C API via CGO, matching the pattern of existing language bindings (rust/, java/, csharp/). Covers session management, typed/untyped tensor I/O, string tensors, IO binding, model metadata, run options, session options with CUDA and TensorRT execution providers, context cancellation, and concurrent inference. Tested with real Qwen3-Embedding-0.6B ONNX INT8 model.
Author
|
@microsoft-github-policy-service agree |
Fix TOCTOU race in NewIOBinding (use-after-free on concurrent Close), empty outputNames panic in Run/RunWithOptions, and metadata use-after- close segfault. Add 15 tests covering all identified coverage gaps. Remove dead code. Bump minimum ORT version to 1.27.0 for getter APIs.
Author
|
Updated with a second commit:
|
Try API version 27 first, fall back to 17 for ORT 1.17-1.26. GetExecutionMode and IsMemPatternEnabled return a clear error on older libraries instead of crashing. Minimum ORT is now 1.17.
Author
|
Added API version fallback: bindings now try API version 27 first, fall back to 17 for older ORT libraries. This means ORT 1.17+ is supported (previously required 1.27+). The two getter functions added in 1.27 (GetExecutionMode, IsMemPatternEnabled) return a clear error on older libraries instead of crashing. |
Add GetVersion/APIVersion, cache CString names in Session for zero- alloc Run hot path, enable/disable telemetry, SetOptimizedModelFilePath, RegisterCustomOpsLibrary, Has/GetSessionConfigEntry, NewSequence, NewMap, NewMapFromGoMap. Fix API version probing to try 27 down to 17 for broader ORT compatibility. Fix IsSequence/IsMap enum values.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces an official Go (cgo) binding layer for the ONNX Runtime C API, including runtime library loading, core session/tensor APIs, and a fairly comprehensive Go test suite with small ONNX model fixtures.
Changes:
- Added Go package
go/onnxruntimewith wrappers for environment lifecycle, sessions, tensors (typed + raw-bytes + string), session/run options, IO binding, and model metadata. - Added a C shim layer (
cshim.h/cshim.c) to call through theOrtApivtable (since cgo can’t call function pointers directly). - Added test models and Go unit/integration tests covering common workflows (including dynamic shapes, zero-length dims, concurrency, and optional Qwen3 integration).
Reviewed changes
Copilot reviewed 29 out of 34 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| go/testdata/gen_models.py | Generates minimal ONNX models used by the Go tests. |
| go/onnxruntime/types.go | Defines Go enums/types for tensor dtypes, graph optimization level, execution mode, and generic tensor element constraints. |
| go/onnxruntime/tensor.go | Implements typed tensors, raw-byte tensors, sequence/map wrappers, and tensor shape/type helpers. |
| go/onnxruntime/tensor_test.go | Unit tests for tensor creation, accessors, and sequence/map helpers. |
| go/onnxruntime/string_tensor.go | Implements string tensor creation and string extraction. |
| go/onnxruntime/string_tensor_test.go | Unit tests for string tensor creation and reading. |
| go/onnxruntime/session.go | Implements session creation, IO introspection, Run/RunWithOptions, profiling end, and lifecycle management. |
| go/onnxruntime/session_test.go | Session tests covering model IO, inference, dynamic/zero-len dims, concurrency, cancellation, and options. |
| go/onnxruntime/run_options.go | Run options wrapper (verbosity, severity, tag, terminate, config entries). |
| go/onnxruntime/run_options_test.go | Tests for run options and RunWithOptions behavior. |
| go/onnxruntime/qwen3_test.go | Optional integration test for a locally available Qwen3 embedding model. |
| go/onnxruntime/ort.go | Global initialization/shutdown, telemetry toggles, provider enumeration, and library path resolution. |
| go/onnxruntime/ort_test.go | Package TestMain + tests for init/idempotency, providers, telemetry, shutdown constraints, and OrtError typing. |
| go/onnxruntime/options.go | SessionOptions wrapper including EP configuration, profiling, free-dim overrides, getters (API-gated), and misc config. |
| go/onnxruntime/options_test.go | Tests for SessionOptions cloning, memory settings, execution mode, getters, profiling, EP errors, and config entries. |
| go/onnxruntime/onnxruntime_error_code.h | Error-code header copy for the shim/include surface. |
| go/onnxruntime/metadata.go | Model metadata wrapper (producer, graph, domain, description, version, custom KV). |
| go/onnxruntime/metadata_test.go | Tests for metadata access, double-close, and use-after-close behavior. |
| go/onnxruntime/load_windows.go | Windows dynamic loading via LoadDLL + FindProc. |
| go/onnxruntime/load_unix.go | Unix dynamic loading via dlopen/dlsym. |
| go/onnxruntime/iobinding.go | IO binding API (bind inputs/outputs, run, fetch bound outputs/names). |
| go/onnxruntime/iobinding_test.go | IO binding tests (basic run, output binding modes, output names/values, memory info, closed session). |
| go/onnxruntime/errors.go | Go error mapping for OrtStatus + wrapping helpers. |
| go/onnxruntime/doc.go | Package-level documentation for initialization and concurrency expectations. |
| go/onnxruntime/cshim.h | Declares the C shim surface used by cgo. |
| go/onnxruntime/cshim.c | Implements shim functions that forward to OrtApi function pointers. |
| go/go.mod | Declares Go module path and minimum Go version. |
| go/.golangci.yml | Lint configuration for the Go code. |
| go/.gitignore | Ignores local .ort-lib/ test library directory. |
Comment on lines
+203
to
+211
| func checkInit() error { | ||
| if !initialized { | ||
| if shutdown { | ||
| return errShutdown | ||
| } | ||
| return errNotInitialized | ||
| } | ||
| return nil | ||
| } |
Comment on lines
+131
to
+142
| count := shapeElementCount(t.shape) | ||
| if count == 0 { | ||
| return nil, nil | ||
| } | ||
|
|
||
| var dataPtr unsafe.Pointer | ||
| if err := checkStatus(C.ort_GetTensorMutableData(t.value, &dataPtr)); err != nil { | ||
| return nil, wrapErr("tensor data", err) | ||
| } | ||
|
|
||
| return unsafe.Slice((*T)(dataPtr), count), nil | ||
| } |
Comment on lines
+150
to
+162
| count := shapeElementCount(t.shape) | ||
| if count == 0 { | ||
| return nil, nil | ||
| } | ||
|
|
||
| var dataPtr unsafe.Pointer | ||
| if err := checkStatus(C.ort_GetTensorMutableData(t.value, &dataPtr)); err != nil { | ||
| return nil, wrapErr("tensor bytes", err) | ||
| } | ||
|
|
||
| nbytes := int(count) * elemSize(t.dtype) | ||
| return unsafe.Slice((*byte)(dataPtr), nbytes), nil | ||
| } |
Comment on lines
+390
to
+402
| func shapeElementCount(shape []int64) int64 { | ||
| if len(shape) == 0 { | ||
| return 1 | ||
| } | ||
| count := int64(1) | ||
| for _, d := range shape { | ||
| if d < 0 { | ||
| return -1 | ||
| } | ||
| count *= d | ||
| } | ||
| return count | ||
| } |
Comment on lines
+91
to
+96
| var bufPtr unsafe.Pointer | ||
| if totalLen > 0 { | ||
| bufPtr = unsafe.Pointer(&buf[0]) | ||
| } else { | ||
| bufPtr = unsafe.Pointer(&buf) | ||
| } |
Comment on lines
+92
to
+108
| func (b *IOBinding) BindInput(name string, value *Tensor) error { | ||
| cName := C.CString(name) | ||
| defer C.free(unsafe.Pointer(cName)) | ||
| return wrapErr("bind input", checkStatus(C.ort_BindInput(b.handle, cName, value.value))) | ||
| } | ||
|
|
||
| func (b *IOBinding) BindOutput(name string, value *Tensor) error { | ||
| cName := C.CString(name) | ||
| defer C.free(unsafe.Pointer(cName)) | ||
| return wrapErr("bind output", checkStatus(C.ort_BindOutput(b.handle, cName, value.value))) | ||
| } | ||
|
|
||
| func (b *IOBinding) BindOutputToDevice(name string, memInfo *MemoryInfo) error { | ||
| cName := C.CString(name) | ||
| defer C.free(unsafe.Pointer(cName)) | ||
| return wrapErr("bind output to device", checkStatus(C.ort_BindOutputToDevice(b.handle, cName, memInfo.handle))) | ||
| } |
Comment on lines
+19
to
+21
| static const char *ort_dlerror(void) { | ||
| return dlerror(); | ||
| } |
Comment on lines
+50
to
+54
| fn := C.ort_dlsym(handle, sym) | ||
| if fn == nil { | ||
| errMsg := C.GoString(C.ort_dlerror()) | ||
| return nil, fmt.Errorf("dlsym OrtGetApiBase: %s", errMsg) | ||
| } |
Comment on lines
+21
to
+24
| proc, err := dll.FindProc("OrtGetApiBase") | ||
| if err != nil { | ||
| return nil, fmt.Errorf("FindProc OrtGetApiBase: %w", err) | ||
| } |
Comment on lines
+86
to
+89
| expected := int(count) * es | ||
| if len(data) != expected { | ||
| return nil, fmt.Errorf("ort: create tensor: data length %d does not match expected %d bytes", len(data), expected) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Official Go bindings for the ONNX Runtime C API via CGO, following
the pattern of existing language bindings (
rust/,java/,csharp/).Closes #9786
Motivation
Go is widely used for backend services that need ML inference
(embeddings, classification, NLP). There is strong community demand
(#9786, 19 comments) and no official Go support. This PR fills that gap.
What's included
inputs/outputs with dynamic dimension support
CreateTensor[T](Go 1.18+ generics),untyped
NewTensorFromBytes, string tensors, zero-length dimensionsmemory arena, profiling, free dimension overrides
key-value API for all other EPs
context.Contextsupport viaRunOptionsSetTerminateSession.Runsafe for concurrent use (ORT guarantee)Design
Wraps the C API vtable (
OrtApi) through ~80 C shim functions incshim.h/cshim.c— CGO cannot call C function pointers directly.Library loaded at runtime via
dlopen(Linux/macOS) orLoadDLL(Windows). Singleton
OrtEnvper process viasync.Mutex.Zero-copy tensor inputs via
runtime.Pinner. Output tensors wrapORT-allocated memory with views valid until
Close().Module path:
github.com/microsoft/onnxruntime/goPackage:
onnxruntime(import asort)Go 1.26 minimum. stdlib + CGO only — no third-party dependencies.
Tests
46 tests passing with
-race: